package git.soulbgm.redis;

import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具
 *
 * @author SoulBGM
 * @date 2024/02/29
 */
public class RedisTool {

    private final RedisTemplate<String, Object> redisTemplate;
    private final StringRedisTemplate stringRedisTemplate;

    public RedisTool(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
        this.redisTemplate = redisTemplate;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 查看key的剩余生存时间 单位秒
     * 实现命令：ttl key
     *
     * @param key 键
     * @return 以秒为单位，返回给定key的剩余生存时间
     */
    public Long ttl(String key) {
        return ttl(key, TimeUnit.SECONDS);
    }

    /**
     * 查看key的剩余生存时间
     * 实现命令：ttl key
     *
     * @param key      键
     * @param timeUnit 时间单位
     * @return 返回给定key的剩余生存时间
     */
    public Long ttl(String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 设置过期时间（单位秒）
     * Redis实现命令：expire key 过期时间（单位秒）
     *
     * @param key     键
     * @param timeout 过期时间
     * @return 成功true 失败false
     */
    public Boolean expire(String key, long timeout) {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置过期时间
     * Redis实现命令：expire key 过期时间
     *
     * @param key      键
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     * @return 成功true 失败false
     */
    public Boolean expire(String key, long timeout, TimeUnit timeUnit) {
        return redisTemplate.expire(key, timeout, timeUnit);
    }

    /**
     * 删除给定的key
     * Redis实现命令：del key
     *
     * @param key 多个不定数量的键
     * @return 删除成功或失败 成功为true 失败为false
     */
    public Long del(Object... key) {
        if (key == null || key.length == 0) {
            return 0L;
        }
        List<String> keyList = new ArrayList<>(key.length);
        for (Object k : key) {
            keyList.add(String.valueOf(k));
        }
        return del(keyList);
    }

    /**
     * 删除给定的key
     * Redis实现命令：del key
     *
     * @param keyList 多个key的集合
     * @return 删除成功或失败 成功为true 失败为false
     */
    public Long del(Collection<String> keyList) {
        return redisTemplate.delete(keyList);
    }

    /**
     * 查找所有符合给定模式的key
     * Redis实现命令：keys 正则表达式   例如：keys na*
     *
     * @param pattern 正则表达式
     * @return 符合模式是所有key
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 查找是否有此key
     * Redis实现命令：exists key
     *
     * @param key 键
     * @return 有返回true 没有返回false
     */
    public Boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 设置一个key-value（将字符串值 value关联到 key）
     * Redis实现命令：set key value
     *
     * @param key   键
     * @param value 值
     */
    public <T> void set(String key, T value) {
        if (value == null) {
            return;
        }
        if (value.getClass() == String.class) {
            stringRedisTemplate.opsForValue().set(key, (String) value);
        } else {
            redisTemplate.opsForValue().set(key, value);
        }
    }

    /**
     * 增或减量
     * 例如：num初始为1  执行此方法 incr(num,5) 那么此时num就为6  递减则为负数 原子性操作
     * 适合统计，高并发生成订单号，秒杀类的业务逻辑等使用
     * Redis实现命令：incrby key 增或减值
     * Redis实现命令：incr key  (注：没有直接加给定数值的命令  只能每次加1)
     *
     * @param key   键
     * @param value 增或减值
     * @return {@link Long}
     */
    public Long increment(String key, long value) {
        return stringRedisTemplate.opsForValue().increment(key, value);
    }

    /**
     * 增或减量
     * Redis实现命令：incrbyfloat key 增或减值
     *
     * @param key   键
     * @param value 增或减值
     * @return {@link Double}
     */
    public Double increment(String key, double value) {
        return stringRedisTemplate.opsForValue().increment(key, value);
    }

    /**
     * 设置key-value和超时时间（秒）
     * Redis实现命令：set key value ex 超时时间（单位秒）
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间（秒）
     */
    public <T> void set(String key, T value, long timeout) {
        set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置key-value和超时时间
     * Redis实现命令：set key value ex 超时时间
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public <T> void set(String key, T value, long timeout, TimeUnit timeUnit) {
        if (value.getClass() == String.class) {
            stringRedisTemplate.opsForValue().set(key, (String) value, timeout, timeUnit);
        } else {
            redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
        }
    }

    /**
     * setnx的工作方式与set完全一样，唯一的区别是，如果键已经存在返回false，则不执行任何操作。 setnx实际上意味着“如果不存在set”
     * Redis实现命令：setnx key value 返回1 set成功 返回0 set失败
     *
     * @param key   键
     * @param value 值
     * @return 成功与否
     */
    public Boolean setNx(String key, Object value) {
        if (value.getClass() == String.class) {
            return stringRedisTemplate.opsForValue().setIfAbsent(key, (String) value);
        } else {
            return redisTemplate.opsForValue().setIfAbsent(key, value);
        }
    }

    /**
     * 这个命令类似于以下两个命令
     * set key value
     * expire key seconds  # 设置生存时间
     * 不同之处是，setnx 是一个原子性(atomic)操作，关联值和设置生存时间两个动作会在同一时间内完成，该命令在 Redis 用作缓存时，非常实用
     * key 已经存在时，setex 覆盖旧值
     * Redis实现命令：setex key value 过期时间（单位秒）
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间（秒）
     */
    public void setEx(String key, Object value, long timeout) {
        set(key, value, timeout);
    }

    /**
     * Redis 中实现锁的简单方法
     * Redis实现命令：set key value NX EX 过期时间（单位秒）
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间（秒）
     * @return 如果服务器返回 OK ，那么这个客户端获得锁，如果服务器返回 NIL ，那么客户端获取锁失败，可以在稍后再重试。
     */
    public Boolean setNxEx(String key, Object value, long timeout) {
        if (value.getClass() == String.class) {
            return stringRedisTemplate.opsForValue().setIfAbsent(key, (String) value, timeout, TimeUnit.SECONDS);
        } else {
            return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 返回 key所关联的字符串值
     * Redis实现命令：get key
     *
     * @param key 键
     * @return key对应的值
     */
    public String getStr(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 返回 key所关联的字符串值 序列化方式
     * Redis实现命令：get key
     *
     * @param key 键
     * @return key对应的实例对象
     */
    public <T> T get(String key) {
        return (T) redisTemplate.opsForValue().get(key);
    }

    /**
     * 返回 key所关联的字符串值 并set进一个新值 属于一个原子性操作
     * Redis实现命令：getset key value		注：如果原本没有key则(nil)
     *
     * @param key   键
     * @param value 值
     * @return 上一次执行的值
     */
    public String getAndSetStr(String key, String value) {
        return stringRedisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 返回 key所关联的字符串值 并set进一个新值 属于一个原子性操作
     * Redis实现命令：getset key value		注：如果原本没有key则(nil)
     *
     * @param key   键
     * @param value 值
     * @return 上一次执行的值
     */
    public <T> T getAndSet(String key, Object value) {
        return (T) redisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 将哈希表 key中的域 field的值设为 value
     * Redis实现命令：hset key field value
     *
     * @param key   RedisKey
     * @param field 哈希表中的key
     * @param value 哈希表中的key对于的value
     */
    public <T> void hset(String key, String field, T value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 散列增或减量
     * Redis实现命令：hincrby key field increment
     *
     * @param key     RedisKey
     * @param hashKey 哈希表中的key
     * @param value   值
     * @return {@link Long}
     */
    public Long hashIncrement(String key, String hashKey, long value) {
        return stringRedisTemplate.opsForHash().increment(key, hashKey, value);
    }

    /**
     * 散列增或减量
     * Redis实现命令：hincrbyfloat key field increment
     *
     * @param key     RedisKey
     * @param hashKey 哈希表中的key
     * @param value   值
     * @return {@link Double}
     */
    public Double hashIncrement(String key, String hashKey, double value) {
        return stringRedisTemplate.opsForHash().increment(key, hashKey, value);
    }

    /**
     * 将整个map放哈希表中
     *
     * @param key RedisKey
     * @param map Map
     */
    public void hsetAll(String key, Map<String, ?> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 返回哈希表 key中，所有的field和value
     * Redis实现命令：hgetall key
     *
     * @param key RedisKey
     * @return {@link Map}<{@link String}, {@link T}>
     */
    public <T> Map<String, T> hgetAll(String key) {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.entries(key);
    }

    /**
     * 返回哈希表 key中给定域 field的值
     * Redis实现命令：hget key field
     *
     * @param key   RedisKey
     * @param field 哈希表中的key
     * @return 给定域 field的值
     */
    public <T> T hget(String key, String field) {
        return (T) redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 返回哈希表 key中给定域 field的值
     * Redis实现命令：hget key field
     *
     * @param key     RedisKey
     * @param hashKey 哈希表中的key
     * @return {@link String}
     */
    public String hgetStr(String key, String hashKey) {
        HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
        return opsForHash.get(key, hashKey);
    }

    /**
     * 删除哈希表key中的一个会多个指定域，不存在的域将被忽略
     * Redis实现命令：hdel key field [field ...]
     *
     * @param key   键
     * @param field 不定数量的哈希表中的key
     * @return 删除field的数量
     */
    public Long hdel(String key, Object... field) {
        return redisTemplate.opsForHash().delete(key, field);
    }

    /**
     * 将一个值 value插入到列表 key的头部
     * Redis实现命令：lpush key value
     *
     * @param key   键
     * @param value 值
     * @return 插入值之后列表的长度
     */
    public <T> Long lpush(String key, T value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 将一个值 value插入到列表 key的尾部
     * Redis实现命令：rpush key value
     *
     * @param key   键
     * @param value 值
     * @return 插入值之后列表的长度
     */
    public <T> Long rpush(String key, T value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 获取从 开始位置到结束位置 的value  序列化方式 保证该list中所有的数据属于同一个类
     * Redis实现命令：lrange key start end
     *
     * @param key   键
     * @param start 开始位置
     * @param end   结束位置
     * @return 开始位置到结束位置的实例对象列表
     */
    public <T> List<T> lget(String key, long start, long end) {
        return (List<T>) redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 获取全部列表
     * Redis实现命令：lrange key 0 -1
     *
     * @param key 键
     * @return 全部的实例对象列表
     */
    public <T> List<T> lget(String key) {
        return lget(key, 0, listSize(key));
    }

    /**
     * 获得列表的大小
     *
     * @param key 键
     * @return 列表大小
     */
    public Long listSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 添加Set
     * Redis实现命令：sadd key value1 value2 ...
     *
     * @param key   键
     * @param value 不定数量的值
     * @return 插入值之后列表的长度
     */
    public Long setAdd(String key, Object... value) {
        return redisTemplate.opsForSet().add(key, value);
    }

    /**
     * 删除Set值
     * Redis实现命令：srem key value1 value2 ...
     *
     * @param key   键
     * @param value 不定数量的值
     * @return 删除之后列表的长度
     */
    public Long setRemove(String key, Object... value) {
        return redisTemplate.opsForSet().remove(key, value);
    }

    /**
     * 获得Set的所有值
     * Redis实现命令：smembers key
     *
     * @param key 键
     * @return 全部的实例对象列表
     */
    public <T> Set<T> getSetAll(String key) {
        return (Set<T>) redisTemplate.opsForSet().members(key);
    }

    /**
     * 判断Set中是否存在指定的value
     * Redis实现命令：sismember key value
     *
     * @param key   键
     * @param value 值
     * @return 存在返回true 不存在返回false
     */
    public Boolean setExist(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 返回Set的长度
     * Redis实现命令：scard key
     *
     * @param key 键
     * @return 列表的大小 如果set不存在，返回0
     */
    public Long setSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 随机返回Set中的一个数据
     * Redis实现命令：srandmember key
     *
     * @param key 键
     * @return 随机的值
     */
    public <T> T getSetRandom(String key) {
        return (T) redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 添加ZSet
     * Redis实现命令：zadd key score value
     *
     * @param key   键
     * @param value 值
     * @param score 权重
     * @return 添加成功返回true 否则返回false
     */
    public Boolean zsetAdd(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 添加ZSet到最上面
     * Redis实现命令：zadd key 负的ZSet的长度 value
     *
     * @param key   键
     * @param value 值
     * @return 添加成功返回true 否则返回false
     */
    public Boolean zsetStartAdd(String key, Object value) {
        return redisTemplate.opsForZSet().add(key, value, -zsetSize(key));
    }

    /**
     * 添加ZSet到最下面
     * Redis实现命令：zadd key 大于等于1的数据 value
     *
     * @param key   键
     * @param value 值
     * @return 添加成功返回true 否则返回false
     */
    public Boolean zsetEndAdd(String key, Object value) {
        return redisTemplate.opsForZSet().add(key, value, zsetSize(key));
    }

    /**
     * 删除ZSet值
     * Redis实现命令：zrem key value1 value2 ...
     *
     * @param key   键
     * @param value 不定数量的值
     * @return 删除后的列表大小
     */
    public Long zsetRemove(String key, Object... value) {
        return redisTemplate.opsForZSet().remove(key, value);
    }

    /**
     * 删除ZSet值
     * Redis实现命令：zrem key value1 value2 ...
     *
     * @param key   键
     * @param start 开始位置
     * @param end   结束位置
     * @return 删除后的列表大小
     */
    public Long zsetRemoveRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * 获得ZSet的值
     * Redis实现命令：zrange key start end
     *
     * @param key   键
     * @param start 开始位置
     * @param end   结束位置
     * @return 从开始位置到结束位置列表的实例对象
     */
    public <T> Set<T> zsetRange(String key, long start, long end) {
        return (Set<T>) redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 获得ZSet的所有值
     * Redis实现命令：zrange key 0 -1
     *
     * @param key 键
     * @return 全部列表的实例对象
     */
    public <T> Set<T> zsetAll(String key) {
        return zsetRange(key, 0, zsetSize(key));
    }

    /**
     * 返回ZSet的长度
     * Redis实现命令：scard key
     *
     * @param key 键
     * @return 如果set不存在，返回0
     */
    public Long zsetSize(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 使用Lua脚本调用redis
     *
     * @param script     Lua脚本
     * @param resultType 结果Class类型
     * @param keys       脚本中使用的KEYS
     * @param args       脚本中使用的ARGV
     * @return {@link T}
     */
    public <T> T execute(String script, Class<T> resultType, List<String> keys, Object... args) {
        RedisScript<T> redisScript = new DefaultRedisScript<>(script, resultType);
        Object[] strArgs = Arrays.stream(args).map(Object::toString).toArray(String[]::new);
        return stringRedisTemplate.execute(redisScript, keys, strArgs);
    }

}
